﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TRACv2.Domain.Interfaces.IRepository;
using TRACv2.Common.Enumerations;
using TRACv2.Domain.DTO;
using AutoMapper;

namespace TRACv2.Persistence
{
    // Author: Ryan Redburn
    // Project: TRACv2
    // Date: 3/15/2014

    #region [ Updates ]

    // Author: Ryan Redburn
    // Date: 3/18/2014 - 3/21/2014
    // Revisions: Added filtering functionality to the GetAll() method. Fixed minor spelling and logic errors.

    #endregion

    public class TicketRepository : ITicketRepository
    {
        /// <summary>
        /// Returns all tickets based on filter criteria. If filter is set to false no filters are used.
        /// </summary>
        /// <param name="filter">Whether or not to filter the returned collection based on following parameters.</param>
        /// <param name="contact">Integer Id of ticket contact</param>
        /// <param name="creator">Integer Id of ticket creator</param>
        /// <param name="assigned">Integer Id of ticket assignee</param>
        /// <param name="issue">String issue</param>
        /// <param name="priority">String priority</param>
        /// <param name="status">String status</param>
        /// <param name="startDate">Inclusive date range start</param>
        /// <param name="endDate">Inclusive date range end</param>
        /// <returns>IEnumerable of TicketDto</returns>
        public IEnumerable<TicketDTO> GetAll(bool filter, int? contact = null, int? creator = null, int? assigned = null, Issue? issue = null, Priority? priority = null, Status? status = null, DateTime? startDate = null, DateTime? endDate = null)
        {
            // Filter resources
            StringBuilder sb;
            string eSql = "";
            int initialQueryLength;

            // Construct filtered query if applicable
            if (filter)
            {
                // Validate parameter values

                if (contact != null && contact < 1)
                    throw new ArgumentOutOfRangeException("contact",
                        "If filter is specified, contact does not accept a value less than one.");

                if (creator != null && creator < 1)
                    throw new ArgumentOutOfRangeException("creator",
                        "If filter is specified, creator does not accept a value less than one.");

                if (assigned != null && assigned < 1)
                    throw new ArgumentOutOfRangeException("assigned",
                        "If filter is specified, assigned does not accept a value less than one.");

                if (startDate == null && endDate != null || startDate != null && endDate == null)
                    throw new ArgumentOutOfRangeException("If filter is specified, and invalid date range is not accepted.");

                // Contruct E-SQL query

                sb = new StringBuilder();
                sb.Append("SELECT Ticket.Id, Ticket.Number, Ticket.DateCreated, Ticket.Contact, Ticket.Creator, Ticket.Assigned, Ticket.Issue, Ticket.Priority, Ticket.Status, Ticket.Note FROM Ticket AS Ticket WHERE ");
                initialQueryLength = sb.ToString().Length;

                if (contact != null)
                    sb.Append("Ticket.Contact = ").Append(contact).Append(" AND ");

                if (creator != null)
                    sb.Append("Ticket.Creator = ").Append(creator).Append(" AND ");

                if (assigned != null)
                    sb.Append("Ticket.Assigned = ").Append(assigned).Append(" AND ");

                if (issue != null)
                    sb.Append("Ticket.Issue LIKE '").Append(issue.ToString()).Append("' AND ");

                if (priority != null)
                    sb.Append("Ticket.Priority LIKE '").Append(priority.ToString()).Append("' AND ");

                if (status != null)
                    sb.Append("Ticket.Status LIKE '").Append(status.ToString()).Append("' AND ");

                if (startDate != null && endDate != null)
                    sb.Append("Ticket.DateCreated BETWEEN '").Append(startDate).Append("' AND '").Append(endDate).Append("' AND ");
                
                // Finalize E-SQL query
                if (sb.ToString().Length == initialQueryLength)
                    eSql = sb.ToString().Substring(0, sb.Length - 7);
                else
                    eSql = sb.ToString().Substring(0, sb.Length - 5);
            }

            IEnumerable<TicketDTO> tickets;
            
            // Open entity connection
            using (var context = new TRACEntities())
            {
                // Mapping retrieved dbset to targeted return type

                if (filter) // With filters
                    tickets = Mapper.Map<IEnumerable<TicketDTO>>(context.Tickets.SqlQuery(eSql));
                else // Without filters
                    tickets = Mapper.Map<IEnumerable<TicketDTO>>(context.Tickets);
            }

            return tickets;
        }

        /// <summary>
        /// Returns a ticket matching the given number. Throws an execption if no ticket is found.
        /// </summary>
        /// <param name="number">String ticket number</param>
        /// <returns>TicketDto</returns>
        public TicketDTO GetByNumber(string number)
        {
            // Throws an exception for invalid number value
            if (String.IsNullOrWhiteSpace(number))
                throw new ArgumentNullException("number", "number does not accept a null or empty string as an argument.");

            TicketDTO ticket;

            // Open entity connection
            using (var context = new TRACEntities())
            {
                // Mapping retrieved entity to targeted return type
                ticket = Mapper.Map<TicketDTO>(context.Tickets.SingleOrDefault(t => t.Number == number));

                // Throws an exception if no ticket is found
                if (ticket == null)
                    throw new InvalidOperationException("No ticket matching the given number was found.");
            }

            return ticket;
        }

        /// <summary>
        /// Submits a new ticket for persistence.
        /// </summary>
        /// <param name="ticket">New ticket to save</param>
        public void Add(TicketDTO ticket)
        {
            // Throws an exception for an invalid dto
            if (ticket == null)
                throw new ArgumentNullException("ticket", "ticket does not accept a null dto as an argument.");

            // Open entity connection
            using (var context = new TRACEntities())
            {
                // Adding ticket via procedure
                context.AddTicket(ticket.DateCreated, ticket.Contact, ticket.Creator, ticket.Assigned,
                    ticket.Issue.ToString(), ticket.Priority.ToString(), ticket.Status.ToString(), ticket.Note);
            }
        }

        /// <summary>
        /// Updates an exsiting ticket. Throws an exception if no matching ticket is found.
        /// </summary>
        /// <param name="ticket">Ticket to udpate</param>
        public void Update(TicketDTO ticket)
        {
            // Throws an exception for an invalid dto
            if (ticket == null)
                throw new ArgumentNullException("ticket", "ticket does not accept a null dto as an argument.");

            // Open entity connection
            using (var context = new TRACEntities())
            {
                // Check if the given ticket exists
                // Throws an exception if no ticket matching the given is found
                if (context.Tickets.SingleOrDefault(t => t.Number == ticket.Number) == null)
                    throw new InvalidOperationException("No ticket matching the given was found.");

                // Update existing ticket using procedure
                context.UpdateTicket(ticket.Number, ticket.Contact, ticket.Creator, ticket.Assigned,
                    ticket.Issue.ToString(), ticket.Priority.ToString(), ticket.Status.ToString(), ticket.Note);
            }
        }

        /// <summary>
        /// Deletes an existing ticket. Throws an exception if no ticket matching the given is found.
        /// </summary>
        /// <param name="ticket">Ticket to delete</param>
        public void Delete(TicketDTO ticket)
        {
            // Throws an exception for an invalid dto
            if (ticket == null)
                throw new ArgumentNullException("ticket", "ticket does not accept a null dto as an argument.");

            // Open entity connection
            using (var context = new TRACEntities())
            {
                // Check if the given ticket exists
                // Throws an exception if no ticket matching the given is found
                if (context.Tickets.SingleOrDefault(t => t.Number == ticket.Number) == null)
                    throw new InvalidOperationException("No ticket matching the given was found.");

                // Delete ticket via procedure
                context.DeleteTicket(ticket.Number);
            }
        }

        /// <summary>
        /// Archives an existing ticket. Throws an exception if no ticket matching the given is found.
        /// </summary>
        /// <param name="ticket">Ticket to archive</param>
        public void Archive(TicketDTO ticket)
        {
            // Throws an exception for an invalid dto
            if (ticket == null)
                throw new ArgumentNullException("ticket", "ticket does not accept a null dto as an argument.");

            // Open entity connection
            using (var context = new TRACEntities())
            {
                // Check if the given ticket exists
                // Throws an exception if no ticket matching the given is found
                if (context.Tickets.SingleOrDefault(t => t.Number == ticket.Number) == null)
                    throw new InvalidOperationException("No ticket matching the given was found.");

                // Archive ticket via procedure
                context.ArchiveTicket(ticket.Number);
            }
        }
    }
}
